﻿using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace Kyoh.Lib.Component.Traces
{
    /// <summary><see cref="TraceCollection"/> に対してトレースデータを設定するためのリスナを定義します。</summary>
    public class TraceCollectionListener : TraceListener
    {
        private readonly TraceCollection _manager;
        /// <summary><see cref="TraceCollectionListener"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="manager">出力先の <see cref="TraceCollection"/> オブジェクト。</param>
        internal TraceCollectionListener(TraceCollection manager)
            : base()
        {
            _manager = manager;

            _syncRoot = new object();
        }

        /// <summary><see cref="TraceCollectionListener"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="manager">出力先の <see cref="TraceCollection"/> オブジェクト。</param>
        /// <param name="listenerName">リスナへの登録に使用する名前。</param>
        internal TraceCollectionListener(TraceCollection manager, string listenerName)
            : base(listenerName)
        {
            _manager = manager;

            _syncRoot = new object();
        }

        /// <summary>出力先としてアタッチしている <see cref="Control"/> オブジェクトを取得します。</summary>
        public Control AttachedControl { get; private set; }
        /// <summary>出力先としてアタッチしている <see cref="TextBox"/> オブジェクトを取得します。</summary>
        public TextBox AttachedTextBox { get { return AttachedControl as TextBox; } }
        /// <summary>出力先としてアタッチしている <see cref="Label"/> オブジェクトを取得します。</summary>
        public Label AttachedLabel { get { return AttachedControl as Label; } }

        /// <summary>出力先としてアタッチしている <see cref="Control"/> クラスの種類を示す <see cref="TraceControlType"/> 値を取得します。</summary>
        public TraceControlType AttachedControlType { get; private set; }

        /// <summary>出力先として <see cref="Control"/> オブジェクトをアタッチします。</summary>
        /// <param name="control">出力先としてアタッチする <see cref="Control"/> オブジェクト。</param>
        public void AttachControl(Control control)
        {
            lock (_syncRoot)
            {
                AttachedControl = control;
                if (AttachedTextBox != null)
                    AttachedControlType = TraceControlType.TextBox;
                else if (AttachedLabel != null)
                    AttachedControlType = TraceControlType.Label;
                else if (control == null)
                    AttachedControlType = TraceControlType.None;
                else
                    throw new ArgumentException(string.Format(Properties.ExceptionMessage.NotSupportedControlAttached, control.GetType(), typeof(TraceCollectionListener).Name), "control");
            }
        }

        /// <summary>出力先としてアタッチされている <see cref="Control"/> オブジェクトを解放します。</summary>
        public void DetachControl()
        {
            lock (_syncRoot)
            {
                AttachedControl = null;
                AttachedControlType = TraceControlType.None;
            }
        }

        private readonly object _syncRoot;

        private delegate void VoidAction();

        /// <summary>内部バッファの値を出力します。</summary>
        [DebuggerStepThrough]
        public override void Flush()
        {
        }

        internal void Clear()
        {
            // Control のクリア
            if (AttachedTextBox != null)
                AttachedTextBox.Invoke(new VoidAction(() => AttachedTextBox.Clear()));
            else if (AttachedLabel != null)
                AttachedLabel.Invoke(new VoidAction(() => AttachedLabel.Text = string.Empty));
        }

        /// <summary>メッセージを出力します。</summary>
        /// <param name="message">出力するメッセージ。</param>
        [DebuggerStepThrough]
        public override void Write(string message)
        {
            lock (_syncRoot)
            {
                if (base.NeedIndent)
                {
                    base.NeedIndent = false;
                    message = new string(' ', base.IndentLevel * base.IndentSize) + message;
                }
                // Control の更新
                if (AttachedTextBox != null)
                    AttachedTextBox.Invoke(new VoidAction(() => { AttachedTextBox.AppendText(message); }));
                else if (AttachedLabel != null)
                    AttachedLabel.Invoke(new VoidAction(() => AttachedLabel.Text += message));
            }
        }

        /// <summary>メッセージを改行つきで出力します。</summary>
        /// <param name="message">出力するメッセージ。</param>
        [DebuggerStepThrough]
        public override void WriteLine(string message)
        {
            Write(message + Environment.NewLine);
        }

        /// <summary>例外をメッセージとして出力します。</summary>
        /// <param name="exc">出力する例外。</param>
        public void Exception(Exception exc)
        {
            var traceData = _manager.NewTraceData();
            traceData.Source = exc.Source;
            traceData.Message = exc.Message;
            traceData.Callstack = exc.StackTrace;
            traceData.SetDetailData(exc.Data);
        }

        /// <summary>エラーメッセージと詳細エラーメッセージを出力します。</summary>
        /// <param name="message">エラーメッセージ。</param>
        /// <param name="detailMessage">詳細エラーメッセージ。</param>
        [DebuggerStepThrough]
        public override void Fail(string message, string detailMessage)
        {
            _manager.AddTraceData(0, TraceEventType.Critical, Properties.Resources.TraceDataFailHeader + ": " + message, detailMessage, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, Guid.Empty, string.Empty);
            base.Fail(message, detailMessage);
        }

        /// <summary>トレースデータを出力します。</summary>
        /// <param name="eventCache">現在のプロセス ID、スレッド ID、およびスタック トレース情報を格納している <see cref="TraceEventCache"/> オブジェクト。</param>
        /// <param name="source">出力を識別するために使用される名前。通常は、トレース イベントを生成したアプリケーションの名前。</param>
        /// <param name="eventType">トレースを発生させたイベントのタイプを指定する <see cref="TraceEventType"/> 値の1つ。</param>
        /// <param name="id">イベントの数値識別子。</param>
        /// <param name="data">出力するトレースデータ。</param>
        [DebuggerStepThrough]
        public override void TraceData(TraceEventCache eventCache, String source, TraceEventType eventType, int id, object data)
        {
            if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, null, null, data, null))
                return;

            var traceData = _manager.NewTraceData();
            traceData.Source = source;
            traceData.EventType = eventType;
            traceData.Id = id;
            traceData.SetEventCache(TraceOutputOptions, eventCache);
            traceData.SetDetailData(data);
            _manager.Add(traceData);

            base.TraceData(eventCache, source, eventType, id, data);
        }

        /// <summary>トレースデータを出力します。</summary>
        /// <param name="eventCache">現在のプロセス ID、スレッド ID、およびスタック トレース情報を格納している <see cref="TraceEventCache"/> オブジェクト。</param>
        /// <param name="source">出力を識別するために使用される名前。通常は、トレース イベントを生成したアプリケーションの名前。</param>
        /// <param name="eventType">トレースを発生させたイベントのタイプを指定する <see cref="TraceEventType"/> 値の1つ。</param>
        /// <param name="id">イベントの数値識別子。</param>
        /// <param name="data">出力するトレースデータ。</param>
        [DebuggerStepThrough]
        public override void TraceData(TraceEventCache eventCache, String source, TraceEventType eventType, int id, params object[] data)
        {
            if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, null, null, null, data))
                return;

            var traceData = _manager.NewTraceData();
            traceData.Source = source;
            traceData.EventType = eventType;
            traceData.Id = id;
            traceData.SetEventCache(TraceOutputOptions, eventCache);
            traceData.SetDetailData(data);
            _manager.Add(traceData);

            base.TraceData(eventCache, source, eventType, id, data);
        }

        /// <summary>トレースデータを出力します。</summary>
        /// <param name="eventCache">現在のプロセス ID、スレッド ID、およびスタック トレース情報を格納している <see cref="TraceEventCache"/> オブジェクト。</param>
        /// <param name="source">出力を識別するために使用される名前。通常は、トレース イベントを生成したアプリケーションの名前。</param>
        /// <param name="eventType">トレースを発生させたイベントのタイプを指定する <see cref="TraceEventType"/> 値の1つ。</param>
        /// <param name="id">イベントの数値識別子。</param>
        /// <param name="message">出力するメッセージ。</param>
        [DebuggerStepThrough]
        public override void TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, int id, string message)
        {
            if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, message, null, null, null))
                return;

            var traceData = _manager.NewTraceData();
            traceData.Source = source;
            traceData.EventType = eventType;
            traceData.Id = id;
            traceData.Message = message;
            traceData.SetEventCache(TraceOutputOptions, eventCache);
            _manager.Add(traceData);

            base.TraceEvent(eventCache, source, eventType, id, message);
        }

        /// <summary>トレースデータを出力します。</summary>
        /// <param name="eventCache">現在のプロセス ID、スレッド ID、およびスタック トレース情報を格納している <see cref="TraceEventCache"/> オブジェクト。</param>
        /// <param name="source">出力を識別するために使用される名前。通常は、トレース イベントを生成したアプリケーションの名前。</param>
        /// <param name="eventType">トレースを発生させたイベントのタイプを指定する <see cref="TraceEventType"/> 値の1つ。</param>
        /// <param name="id">イベントの数値識別子。</param>
        /// <param name="format">0個以上の書式項目を格納している書式指定文字列。<paramref name="args"/> 配列内のオブジェクトに対応します。</param>
        /// <param name="args">0個以上の書式設定対象オブジェクトを含んだ object 配列。</param>
        [DebuggerStepThrough]
        public override void TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, int id, string format, params object[] args)
        {
            if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, format, args, null, null))
                return;

            var traceData = _manager.NewTraceData();
            traceData.Source = source;
            traceData.EventType = eventType;
            traceData.Id = id;
            traceData.SetEventCache(TraceOutputOptions, eventCache);
            traceData.SetMessageData(format, args);
            _manager.Add(traceData);

            base.TraceEvent(eventCache, source, eventType, id, format, args);
        }

        /// <summary>トレースデータを出力します。</summary>
        /// <param name="eventCache">現在のプロセス ID、スレッド ID、およびスタック トレース情報を格納している <see cref="TraceEventCache"/> オブジェクト。</param>
        /// <param name="source">出力を識別するために使用される名前。通常は、トレース イベントを生成したアプリケーションの名前。</param>
        /// <param name="id">イベントの数値識別子。</param>
        /// <param name="message">出力するメッセージ。</param>
        /// <param name="relatedActivityId">関連するアクティビティを識別する <see cref="Guid"/> オブジェクト。</param>
        [DebuggerStepThrough]
        public override void TraceTransfer(TraceEventCache eventCache, String source, int id, string message, Guid relatedActivityId)
        {
            var traceData = _manager.NewTraceData();
            traceData.Source = source;
            traceData.Id = id;
            traceData.Message = message;
            traceData.SetEventCache(TraceOutputOptions, eventCache);
            traceData.RelatedActivityId = relatedActivityId;
            _manager.Add(traceData);

            base.TraceTransfer(eventCache, source, id, message, relatedActivityId);
        }
    }
}
